package com.hzgj.bcl.soa.cicada.lb;

import com.google.common.collect.Lists;

import com.hzgj.bcl.soa.cicada.sender.Sender;
import com.hzgj.bcl.soa.config.ReferenceDef;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class RoundRobinRule extends AbstractRule {


    private AtomicInteger nextServerCyclicCounter = new AtomicInteger(0);

    @Override
    public Sender choose(LoadBalancer lb, Object key) {
        Sender sender = null;
        int count = 0;
        RuleObject ruleObject = (RuleObject) key;
        ReferenceDef referenceDef = new ReferenceDef();
        referenceDef.setService(ruleObject.getService());
        while (sender == null && count++ < 10) {
            List<Sender> reachableServers = Lists.newArrayList(lb.getRouter().getUpSenders(referenceDef));
            List<Sender> markedDownSenders = Lists.newArrayList(lb.getRouter().getMarkedDownSenders(referenceDef));
            int upCount = reachableServers.size();
            int serverCount = markedDownSenders.size();

            if ((upCount == 0) && (serverCount == 0)) {
                logger.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            if (upCount != 0) {
                sender = reachableServers.get(incrementAndGetModulo(upCount));
            }
            if (sender == null && serverCount != 0) {
                sender = markedDownSenders.get(incrementAndGetModulo(serverCount));
            }

            if (sender != null) {
                return sender;
            }
        }
        if (count >= 10) {
            logger.warn("No available alive servers after 10 tries from load balancer: " + lb);
        }
        return sender;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (; ; ) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next)) {
                return next;
            }
        }
    }

    @Override
    public String getName() {
        return "roundRobin";
    }
}
